
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart' as gt;
import 'package:logger/logger.dart';
import 'package:xim/common/util/ecc.dart';
import 'package:xim/config/store_config.dart';
import 'package:xim/http/apis/user/user_api.dart';
import 'package:xim/http/store.dart';

var logger = Logger();
var baseIp = "192.168.0.108";

/// 请求超时时间
int requestTimeout = 30;

/// 服务器返回的数据格式
class ResponseResult<T> {

  static const successCode = 0;
  static const loginOffline = 3004;

  String message;

  int code;

  T data;

  ResponseResult(this.message, this.code, this.data);

  Map<String, dynamic> toJson() => {
    "message": message,
    "code": code,
    "data": data,
  };

  ResponseResult.fromJson(Map<String, dynamic> json)
      :message = json['message'],
        code = json['code'],
        data = json['data']
  ;

  bool isSuccess(){
    return code==0;
  }

}

Dio? _instance;

Dio get dio {
  if(_instance != null) {
    return _instance!;
  }
  _instance = Dio(
    BaseOptions(
      baseUrl: 'http://$baseIp:9000',
      connectTimeout: Duration(seconds: requestTimeout),
      receiveTimeout: Duration(seconds: requestTimeout),
      responseType: ResponseType.json,
    ),
  );
  _instance?.interceptors.add(LogInterceptor(
      responseBody: true,
      error: true,
      requestHeader: false,
      responseHeader: false,
      request: false,
      requestBody: true));
  // _instance?.interceptors.add(CacheInterceptor());
  _instance?.interceptors.add(AuthInterceptor());
  _instance?.interceptors.add(ErrorInterceptor());
  // _instance?.interceptors.add(RespInterceptor());
  _instance?.transformer = BackgroundTransformer();
  return _instance!;
}

class AuthInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    var userToken = await StoreApi.instance.takeUserToken();
    options.headers.addIf(!options.headers.containsKey("token"), "token", userToken);
    return super.onRequest(options, handler);
  }
}

class RespInterceptor extends Interceptor {

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // response
    return super.onResponse(response, handler);
  }

}
class ErrorInterceptor extends Interceptor {

  late RequestOptions options;

  // @override
  // void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
  //   this.options = options;
  // }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    void toast(String msg){
      Fluttertoast.showToast(
          msg: msg,
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.BOTTOM,
          timeInSecForIosWeb: 1,
          backgroundColor: const Color(0xfff0f0f0),
          textColor: const Color(0xff4f4f4f),
          fontSize: 14.0
      );
    }
    EasyLoading.dismiss();
    var errorType = err.type;
    if(errorType == DioExceptionType.cancel){
      logger.d("dio network exception: ${err.message}");
      toast("请求取消");
    }else if(errorType == DioExceptionType.connectionTimeout){
      logger.d("dio network timeout: ${err.message}");
      toast("连接超时");
    }else if(errorType == DioExceptionType.sendTimeout){
      logger.d("dio network timeout: ${err.message}");
      toast("请求超时");
    }else if(errorType ==DioExceptionType.receiveTimeout){
      toast("响应超时");
    } else{
      logger.d("dio exception: ${err.message}");
      toast("网络异常");
    }
    return super.onError(err, handler);
  }

}

class AjaxResultInterceptor extends Interceptor {
  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {}
}

extension DioExtension on Dio {

  Future<ResponseResult<T>> ajaxPost<T>(String path,
      {Object? data,
        Map<String, dynamic>? queryParameters,
        // void Function()? beforeFunc,
        void Function(T data)? successFunc,
        void Function(int code, String msg)? errorFunc,
        void Function(bool isSuccess, Response<T> res)? completeFunc}) async {

    Future<Response<T>> request(){
      return dio.post(path, data: data, queryParameters: queryParameters);
    }

    ResponseResult<T> parseResponse(Response<T> resp){
      if(resp.statusCode!=200){
        throw Exception("request error"
            "[response status:${resp.statusCode}, response message:${resp.data}, request path: $path]");
      }
      // 解析结构
      var responseResult = resp.data as Map<String, dynamic>;
      var code = responseResult['code'];
      var message = responseResult['message'];
      var respData = responseResult['data'];
      return  ResponseResult<T>(message, code, respData);
    }
    // 重试次数
    var requestCount = 3;
    var resp = await request();
    while(resp.statusCode!=200 && requestCount>0){
      // try again
      resp = await request();
      requestCount--;
    }
    // 解析结构
    var result = parseResponse(resp);
    if(result.code == ResponseResult.successCode){
      return result;
    }
    if(result.code == ResponseResult.loginOffline){
      // 重新认证一次
      await UserApi.userLogin();
      resp = await request();
      result = parseResponse(resp);
    }
    return result;
  }

  Future<ResponseResult> postResult(
      String path, {
        Object? data,
        Map<String, dynamic>? queryParameters,
        Options? options,
        CancelToken? cancelToken,
        ProgressCallback? onSendProgress,
        ProgressCallback? onReceiveProgress,
        bool cached = false,
      }) async {
    return ajaxPost(path, data: data, queryParameters: queryParameters);
    // options ??= Options();
    // var responseResult = await dio.post(
    //   path,
    //   data: data,
    //   queryParameters: queryParameters,
    //   options: cached ? options.addHeader("cached", true) : options,
    //   cancelToken: cancelToken,
    //   onSendProgress: onSendProgress,
    //   onReceiveProgress: onReceiveProgress,
    // );
    // if (responseResult.statusCode != 200) {
    //   logger.w("dio request status exception：${responseResult
    //       .statusCode}, url: $path, error: ${responseResult.statusMessage}");
    // }
    // return ResponseResult.fromJson(responseResult.data);
  }

}

/// api 缓存
class CacheInterceptor extends Interceptor {

  Set<String> cacheUrls = {};

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    var path = options.uri.path;
    var headers = options.headers;
    var isCached = headers['cached'] as bool?;
    if(isCached==null || isCached==false){
      handler.next(options);
    }else{
      // cache
      var cachedResp = HttpCacheManager.instance.tryGetCache(path);
      if(cachedResp==null){
        // send request
        cacheUrls.add(path);
        handler.next(options);
      }else{
        // response cached result
        handler.resolve(cachedResp);
        logger.i("hit cache $path");
      }
    }
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    var path = response.requestOptions.uri.path;
    if(cacheUrls.contains(path)){
      // add cache
      HttpCacheManager.instance.addCache(path, response);
      cacheUrls.remove(path);
    }
    handler.next(response);
  }

}

class HttpCache {

  final String path;

  final Duration aliveTime;

  DateTime startTime = DateTime.now();

  Response response;

  int hitTimes = 0;

  HttpCache(
      this.path,
      this.response,
      {required this.startTime, this.aliveTime=const Duration(seconds: 3)}
    );

  void reset() => startTime = DateTime.now();

  /// if expire return true else return false
  bool isExpire(){
    var dt = startTime.add(aliveTime);
    return dt.compareTo(DateTime.now())<0;
  }

}

class HttpCacheManager {

  HttpCacheManager._():cacheSize=1024;

  final int cacheSize;
  final Map<String, HttpCache> cache = {};
  static final HttpCacheManager _instance = HttpCacheManager();
  static  HttpCacheManager get instance => _instance;

  HttpCacheManager({this.cacheSize=1024});

  void addCache(String path, Response response, {bool resetIfExist=true}){
    if(cache.containsKey(path)) {
      cache[path]?.reset();
      return;
    }
    _clearCache();
    cache[path] = HttpCache(path, response, startTime: DateTime.now());
  }

  void _clearCache({int minCount=3}){
    if(cache.length<cacheSize){
      return;
    }
    // 移除过期的数据
    for(var entry in cache.entries){
      if(entry.value.isExpire()){
        cache.remove(entry.key);
      }
    }
    if(cache.length<cacheSize){
      return;
    }
    var items = cache.entries.toList();
    items.sort((e1,e2)=>e1.value.hitTimes - e2.value.hitTimes);
    for(var i=0; i<minCount && i<items.length;i++){
      var key = items[i].key;
      cache.remove(key);
    }
  }

  Response? tryGetCache(String path){
    if(!cache.containsKey(path)){
      return null;
    }
    var isExpire = cache[path]?.isExpire();
    if(isExpire==false){
      // 缓存没有过期
      cache[path]?.hitTimes+=1;
      return cache[path]?.response;
    }
    cache.remove(path);
    return null;
  }
}

extension OptionsExtend on Options{

  Map<String, dynamic> getHeaders(){
    headers ??= {};
    return headers!;
  }

  Options addHeader(String key, dynamic value){
    getHeaders()[key] = value;
    return this;
  }

}
